#include <stdio.h>
#include <string>
#include <cstring>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

using namespace std;

const int NUM= 64;
const int BASESIZE=1024;

char* gargv[NUM];
int gargc;

int LASTCODE;

string GetUser()
{
    char* user= getenv("USER");

    if(user==nullptr)
    {
        return "None";
    }
    else return user;
}

string GetHostName()
{
    char* s= getenv("HOSTNAME");
    if(s==nullptr) return "None";
    else return s;
}

string GetPath()
{
    char buf[BASESIZE];
    if(getcwd(buf,BASESIZE)==nullptr)
    {
        return "None";
    }

    return buf;
}

string MakeCommandLine()
{
    char buf[BASESIZE];
    snprintf(buf, BASESIZE, "%s@%s:%s$ ",GetUser().c_str(),GetHostName().c_str(),GetPath().c_str());
    return buf;
}

void PrintCommandLineTips()
{
    printf("%s",MakeCommandLine().c_str());//printf会给从设备文件对应的struct FILE里面写数据，但由于标准输出的struct FILE的刷新策略是行刷新，没有换行符是不会将数据从struct FILE拷贝到内核缓冲区，自然也就拷不到从设备文件中了，所以命令行提示符不会显示在终端上，但是我们也不能通过加\n来刷新，因为没有谁家的命令行提示符有换行，我们只能用fflush来刷新标准输出从设备的struct FILE了
    fflush(stdout);
}

bool GetCommandLine(char* buffer)
{
    char* s=fgets(buffer,BASESIZE,stdin);
    if(s==nullptr ||  strlen(s)==1)//出错或者读到的是空串
    {
        return false;
    }

    buffer[strlen(buffer)-1]= 0;
    return true;
}

void ParseCommandLine(char* buffer)
{
    //解析前先重置
    memset(gargv, 0 ,sizeof(gargv));
    gargc= 0;
    const char* sep=" ";
    gargv[gargc++] = strtok(buffer, sep);

    while( gargv[gargc++]= strtok(nullptr,sep))
    {}

    gargc--;
}

bool CheckAndExecBuiltCommand()
{
    if(strcmp("cd", gargv[0])==0)
    {
        //是内建命令
        if(gargc==2)
        {
            chdir(gargv[1]);
            string s="PWD=";
            s+=gargv[1];
            putenv((char*)s.c_str());//更新或添加环境变量

            LASTCODE= 0;
        }
        else
        {
            LASTCODE= 1;
        }

        return true;//返回真表示这是一个内建命令
    }
    else if(strcmp(gargv[0], "export") == 0)
    {
        if(gargc==2)
        {
            putenv(gargv[1]);
            LASTCODE= 0;
        }
        else
        {
            LASTCODE= 1;
        }

        return true;
    }
    else if(strcmp(gargv[0], "env") == 0)
    {
        for(int i=0; environ[i] ;i++)
        {
            printf("%s\n",environ[i]);
        }
        LASTCODE= 0;
        return true;
    }
    else if(strcmp(gargv[0], "echo" ) == 0)
    {
        if(gargc== 2)
        {
            if(gargv[1][0]=='$')
            {
                if(gargv[1][1]=='?')
                {
                    printf("%d\n",LASTCODE);
                    LASTCODE=0;
                }
                else
                {
                    LASTCODE= 1;
                }
            }
            else
            {
                printf("%s\n", gargv[1]);
                LASTCODE= 0;
            }

        }
        else
        {
            LASTCODE=1;
        }

        return true;
    }
    else if(strcmp(gargv[0], "pwd")==0)
    {
        char buf[BASESIZE];
        getcwd(buf,BASESIZE);
        printf("%s\n",buf);
        
        LASTCODE=0;
        return true;
    }
    else
    {
        return false;//不是内建命令
    }
}

void ExecuteCommand()
{
    int pid= fork();
    
    if(pid== 0)//子进程
    {
        execvpe(gargv[0], gargv, environ);
    }
    else
    {
        int st;
        waitpid(pid, &st, 0);//阻塞等待

        //更新$?
        if(WIFEXITED(st))
        {
            LASTCODE= WEXITSTATUS(st);
        }
        else//信号终止
        {
            LASTCODE= 128+(st&0x7f);
        }
    }
}

int main()
{

    char commandline_buffer[BASESIZE];

    while(1)
    {
        PrintCommandLineTips();//打印命令行提示符

        if(!GetCommandLine(commandline_buffer))
        {
            continue;
        }

        //走到这里说明成功读到了命令行
        ParseCommandLine(commandline_buffer);

        if(CheckAndExecBuiltCommand())//检查并执行内建命令
        {
            continue;//返回true，是内建命令，那就进入下一波
        }

        ExecuteCommand();
    }
    return 0;
}